#include "database_query_result.h"

#include "database_mysql_head.h"
#include "database_field.h"
#include "database_mysql_wrap.h"
#include "report.h"
#include "log.h"

namespace afcore {
namespace database {

static uint32_t SizeForType(SMysqlField* field) {
  switch(field->type) {
    case MYSQL_TYPE_NULL:
      return 0;
    case MYSQL_TYPE_TINY:
      return 1;
    case MYSQL_TYPE_YEAR:
    case MYSQL_TYPE_SHORT:
      return 2;
    case MYSQL_TYPE_INT24:
    case MYSQL_TYPE_LONG:
    case MYSQL_TYPE_FLOAT:
      return 4;
    case MYSQL_TYPE_DOUBLE:
    case MYSQL_TYPE_LONGLONG:
    case MYSQL_TYPE_BIT:
      return 8;

    case MYSQL_TYPE_TIMESTAMP:
    case MYSQL_TYPE_DATE:
    case MYSQL_TYPE_TIME:
    case MYSQL_TYPE_DATETIME:
      return sizeof(MYSQL_TIME);

    case MYSQL_TYPE_TINY_BLOB:
    case MYSQL_TYPE_MEDIUM_BLOB:
    case MYSQL_TYPE_LONG_BLOB:
    case MYSQL_TYPE_BLOB:
    case MYSQL_TYPE_STRING:
    case MYSQL_TYPE_VAR_STRING:
      return field->max_length + 1;

    case MYSQL_TYPE_DECIMAL:
    case MYSQL_TYPE_NEWDECIMAL:
      return 64;

    case MYSQL_TYPE_GEOMETRY:
      LOG_WARN("SizeForType(), invalid field type {}", static_cast<uint32_t>(field->type));

    /// MYSQL_TYPE_ENUM与MYSQL_TYPE_SET 不通过网络发送
    default:
      return 0;
  }
}

/// @brief  mysql数据类型转换成系统内用字段类型
/// @param  type        mysql数据类型
/// @return 系统内用字段类型
EDatabaseFieldType MysqlTypeToFieldType(enum_field_types type) {
  switch (type) {
    case MYSQL_TYPE_NULL:
      return EDatabaseFieldType::kDatabaseFieldType_Null;
    case MYSQL_TYPE_TINY:
      return EDatabaseFieldType::kDatabaseFieldType_Int8;
    case MYSQL_TYPE_YEAR:
    case MYSQL_TYPE_SHORT:
      return EDatabaseFieldType::kDatabaseFieldType_Int16;
    case MYSQL_TYPE_INT24:
    case MYSQL_TYPE_LONG:
      return EDatabaseFieldType::kDatabaseFieldType_Int32;
    case MYSQL_TYPE_LONGLONG:
    case MYSQL_TYPE_BIT:
      return EDatabaseFieldType::kDatabaseFieldType_Int64;
    case MYSQL_TYPE_FLOAT:
      return EDatabaseFieldType::kDatabaseFieldType_Float;
    case MYSQL_TYPE_DOUBLE:
      return EDatabaseFieldType::kDatabaseFieldType_Double;
    case MYSQL_TYPE_DECIMAL:
    case MYSQL_TYPE_NEWDECIMAL:
      return EDatabaseFieldType::kDatabaseFieldType_Decimal;
    case MYSQL_TYPE_TIMESTAMP:
    case MYSQL_TYPE_DATE:
    case MYSQL_TYPE_TIME:
    case MYSQL_TYPE_DATETIME:
      return EDatabaseFieldType::kDatabaseFieldType_Date;
    case MYSQL_TYPE_TINY_BLOB:
    case MYSQL_TYPE_MEDIUM_BLOB:
    case MYSQL_TYPE_LONG_BLOB:
    case MYSQL_TYPE_BLOB:
    case MYSQL_TYPE_STRING:
    case MYSQL_TYPE_VAR_STRING:
      return EDatabaseFieldType::kDatabaseFieldType_Binary;
    default:
      {
        LOG_WARN("MysqlTypeToFieldType(), invalid field type {}", static_cast<uint32_t>(type));
      }
      break;
  }
  return EDatabaseFieldType::kDatabaseFieldType_Null;
}

CResultSet::CResultSet(SMysqlResult* result, SMysqlField* fields, uint64_t row_count, uint32_t field_count)
  : row_count_(row_count)
  , field_count_(field_count)
  , result_(result)
  , field_(fields) {
  current_row_ = new CField[field_count_];
#if defined(AFCORE_DEBUG)
  for (uint32_t i = 0; i < field_count_; ++i) {
    current_row_[i].SetMetadata(&field_[i], i);
  }
#endif
}

CResultSet::~CResultSet() {
  CleanUp();
}

bool CResultSet::NextRow() {
  MYSQL_ROW row;

  if (!result_) {
    return false;
  }

  row = mysql_fetch_row(result_);
  if (!row) {
    CleanUp();
    return false;
  }

  unsigned long* lengths = mysql_fetch_lengths(result_);
  if (!lengths) {
    LOG_WARN("mysql_fetch_lengths, cannot retrieve value lengths. Error {}", mysql_error(result_->handle));
    CleanUp();
    return false;
  }

  for (uint32_t i = 0; i < field_count_; ++i) {
    current_row_->SetStructuredValue(row[i], MysqlTypeToFieldType(field_[i].type), lengths[i]);
  }

  return true;
}

const CField &CResultSet::operator[](size_t index) const {
  DBG_ASSERT(index < field_count_);
  return current_row_[index];
}

void CResultSet::CleanUp() {
  if (current_row_) {
    delete[] current_row_;
    current_row_ = nullptr;
  }

  if (result_) {
    mysql_free_result(result_);
    result_ = nullptr;
  }
}


CPreparedResultSet::CPreparedResultSet(SMysqlStmt* stmt, SMysqlResult* result, uint64_t row_count, uint32_t field_count)
  : row_count_(row_count)
  , field_count_(field_count)
  , stmt_(stmt)
  , metadata_result_(result) {
  if (!metadata_result_) {
    return;
  }
  if (stmt->bind_result_done) {
    delete[] stmt->bind->length;
    delete[] stmt->bind->is_null;
  }

  bind_ = new SMysqlBind[field_count_];

  /// is_null与length的声明周期与mysql conn一样长。通过mysql_stmt_bind_result函数将
  /// 指针移到stmt_bind中，只有等到stmt->bind_result_done的时候才能析构
  /// !不可随意析构这两个变量
  RMysqlBool* is_null = new RMysqlBool[field_count_];
  unsigned long* length = new unsigned long[field_count_];

  memset(is_null, 0, sizeof(RMysqlBool) * field_count_);
  memset(bind_, 0, sizeof(SMysqlBind) * field_count_);
  memset(length, 0, sizeof(unsigned long) * field_count_);

  // 非0 错误返回
  if (mysql_stmt_store_result(stmt_)) {
    LOG_WARN("mysql_stmt_store_result, cannot bind result from MYSQL server. Error {}", mysql_stmt_error(stmt_));
    // 这里不需要调用clean 因为数据还没有存储成功
    delete[] bind_;
    delete[] is_null;
    delete[] length;
    return;
  }

  row_count_ = mysql_stmt_num_rows(stmt_);
  SMysqlField* field = reinterpret_cast<SMysqlField*>(mysql_fetch_fields(metadata_result_));

  size_t row_size = 0;
  for (uint32_t i = 0; i < field_count_; ++i) {
    uint32_t size = SizeForType(&field[i]);
    row_size += size;

    bind_[i].buffer_type = field[i].type;
    bind_[i].buffer_length = size;
    bind_[i].length = &length[i];
    bind_[i].is_null = &is_null[i];
    bind_[i].error = nullptr;
    bind_[i].is_unsigned = field[i].flags & UNSIGNED_FLAG;
  }

  char* data_buffer = new char[row_size * row_count_];
  for (uint32_t i = 0, offset = 0; i < field_count_; ++i) {
    bind_[i].buffer = data_buffer + offset;
    offset += bind_[i].buffer_length;
  }

  if (mysql_stmt_bind_result(stmt_, bind_)) {
    LOG_WARN("mysql_stmt_bind_result, cannot bind result from MYSQL server. Error {}", mysql_stmt_error(stmt_));
    mysql_stmt_free_result(stmt_);
    CleanUp();
    delete[] is_null;
    delete[] length;
    return;
  }

  rows_.resize(static_cast<uint32_t>(row_count_) * field_count_);
  while (_NextRow()) {
    for (uint32_t f_index = 0; f_index < field_count_; ++f_index) {
      unsigned long buffer_length = bind_[f_index].buffer_length;
      unsigned long fetched_length = *bind_[f_index].length;
      if (!*bind_[f_index].is_null) { // 结构体类型
        void* buffer = stmt_->bind[f_index].buffer;
        switch(bind_[f_index].buffer_type) {
          case MYSQL_TYPE_TINY_BLOB:
          case MYSQL_TYPE_MEDIUM_BLOB:
          case MYSQL_TYPE_LONG_BLOB:
          case MYSQL_TYPE_BLOB:
          case MYSQL_TYPE_STRING:
          case MYSQL_TYPE_VAR_STRING:
            {
              if(fetched_length < buffer_length) { // 因为是二进制 所以获取的长度小于buffer的长度时需要补\0
                *(static_cast<char*>(buffer) + fetched_length) = '\0';
              }
            }
            break;
          default:
            break;
        }
      } else { // 非结构体类型
        rows_[static_cast<uint32_t>(row_pos_) * field_count_ + f_index].SetByteValue(
          nullptr, MysqlTypeToFieldType(bind_[f_index].buffer_type), *bind_[f_index].length);
      }
#if defined(AFCORE_DEBUG)
      rows_[static_cast<uint32_t>(row_pos_) * field_count_ + f_index].SetMetadata(&field[f_index], f_index);
#endif
    }
    ++row_pos_;
  }
  row_pos_ = 0;

  // 数据都处理完了 所以要析构掉准备语句
  mysql_stmt_free_result(stmt_);
}

CPreparedResultSet::~CPreparedResultSet() {
  CleanUp();
}

bool CPreparedResultSet::NextRow() {
  // 只更新 row_pos的位置，数据在rows_中
  return ++row_pos_ < row_count_;
}

CField *CPreparedResultSet::Fetch() const {
  DBG_ASSERT(row_pos_ < row_count_);
  return const_cast<CField*>(&rows_[static_cast<uint32_t>(row_pos_) * field_count_]);
}

const CField &CPreparedResultSet::operator[](size_t index) const {
  DBG_ASSERT(row_pos_ < row_count_);
  DBG_ASSERT(index < field_count_);
  return rows_[static_cast<uint32_t>(row_pos_) * field_count_ + index];
}

void CPreparedResultSet::CleanUp() {
  if (metadata_result_) {
    mysql_free_result(metadata_result_);
  }

  if (bind_) {
    delete[] static_cast<char*>(bind_->buffer);
    delete[] bind_;
    bind_ = nullptr;
  }
}

bool CPreparedResultSet::_NextRow() {
  if (row_pos_ >= row_count_) {
    return false;
  }

  int ret_val = mysql_stmt_fetch(stmt_);
  return 0 == ret_val || MYSQL_DATA_TRUNCATED == ret_val;
}

} // !namespace database
} // !namespace afcore